package ipcards.net;

import ipcards.events.*;
import ipcards.*;
import ipcards.Player;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;


/**
 * Represents a client connected to the server.
 * 
 * @author Ricky Vincent
 */
public class Client implements Runnable, Comparable<Client> {
	
	private static final String END_LINE = "\n";
	
	private Socket socket;
	private DataOutputStream out;
	private BufferedReader in;
	private boolean connected = false;
	private boolean isturn = false;
	
	private int id;
	
	private Table table;
	private ServerModel model;
	private CardsServer cardsserver;
	public Player thisplayer;

	
	/**
	 * Constructs a new <code>Client</code> object with a <code>Socket</code>.
	 * 
	 * @param socket The <code>Socket</code> to bind to this <code>Client</code>.
	 * @param table The {@link Table} this client is at.
	 */
	public Client (Socket socket, Table table, CardsServer cardsserver) {	
		this.cardsserver = cardsserver;
		this.socket = socket;
		this.table = table;
		model = new ServerModel(table, this);
		thisplayer = new Player("new");
		thisplayer.setClient(this);
		
		try {
			out = new DataOutputStream(socket.getOutputStream());
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			connected = true;
			
			//send cards initially
			//TODO use full sync
			model.playerActionPerformed(new PlayerAction(this, PlayerAction.SYNC_LOCATIONS));
			
		} catch (IOException e) {
			System.err.println("Could not connect to client");
		}		
	}
	
	public int getID() {
		return id;
	}
	
	public ServerModel getServerModel()	{
		return model;
	}
	
	/**
	 * Start this thread
	 */
	public void run() {
		receiveData();
	}
	
	public void myTurn(boolean ismyturn) {
		isturn = ismyturn;
	}
	
	/**
	 * Receives data from the <code>Socket</code>. Blocks until data is received, or <code>disconnect</code> is called.
	 */
	private void receiveData() {
	
		String input = null;
		
		while (connected) {
			try {
				input = in.readLine();
				
				//If it is not the players turn, only allow these commands, if it is then allow everything
				if(isturn 	|| input.charAt(0) == PlayerAction.SYNC_LOCATIONS 
							|| input.charAt(0) == PlayerAction.HOLD 
							|| input.charAt(0) == PlayerAction.LOCAL_FLIP) {
					
					commandHandler(input);
				}
				if(input.charAt(0) == PlayerAction.NEW_GAME && input.charAt(1) == PlayerAction.NEW_GAME){
					commandHandler(input);	
				}
				
			} catch (IOException e) {
				
				System.err.println("Lost connection to client... retrying");
				
				try {
					//sleep for 1 second between reconnection attempts
					Thread.sleep(1000);
				} catch (InterruptedException e1) {
					continue;
				}
			}
		}
	}
	
	
	/**
	 * Handles commands. Creates a PlayerAction object and passes it to the ServerModel.
	 * 
	 * @param command the command to handle.
	 */
	private void commandHandler (String command) {

		Dbg.serverLog(command);
		
		PlayerAction newAction = new PlayerAction(this, command.charAt(0), command.substring(1));
		model.playerActionPerformed(newAction);
	}
	
	
	/**
	 * Writes bytes directly to the <code>Socket</code>.
	 * 
	 * @param data the array of bytes to write.
	 * @throws IOException
	 */
	private void sendData (byte[] data) throws IOException {
		out.write(data);
		out.flush();
	}
	
	
	/**
	 * Sends a command to this <code>Client</code>
	 * 
	 * @param command The command to send from {@link PlayerAction}.
	 * @param args The arguments to send with the command.
	 * @throws IOException
	 */
	public void sendCommand(char command, String args) {
		args = command + args + END_LINE;
		try {
			sendData(args.getBytes());
		} catch (IOException e) {
			connected = false;
			e.printStackTrace();
		}
	}
	
	
	/**
	 * Sends a command to all connected clients
	 * @throws IOException 
	 */
	public void sendCommandToAll (char command, String args) {
		
		for(Client client: CardsServer.clients) {
			client.sendCommand(command, args);
		}
	}
	
	
	/**
	 * Sends a command to all connected clients except the one who sent it.
	 * @throws IOException 
	 */
	public void sendCommandToAllX (char command, String args) {
		
		for(Client client: CardsServer.clients) {
			if(client != this)
				client.sendCommand(command, args);
		}
	}

	
	/**
	 * @return the <code>Socket</code>
	 */
	public Socket getSocket() {
		return socket;
	}
	
	
	/**
	 * Disconnects this client.
	 */
	public void disconnect() {
		connected = false;
		CardsServer.clients.remove(this);
	}


	/**
	 * @return the table
	 */
	public Table getTable() {
		return table;
	}


	/**
	 * @param table the table to set
	 */
	public void setTable(Table table) {
		this.table = table;
	}
	
	/**
	 * Ends the current players turn 
	 */
	public void endTurn() {
		if(isturn) {
			myTurn(false);
			cardsserver.nextTurn();
		}
	}
	
	/**
	 * Ends the current players turn, sets turn to the previous player
	 */
	public void endTurnReverse(){
		if(isturn) {
			myTurn(false);
			cardsserver.nextTurnReverse();
		}
	}
	
	public void resetLocalFlip() {
		model.resetLocalFlip();
	}
	
	public int compareTo(Client client) {
		return (client.socket.hashCode()) - (socket.hashCode());
	}
	
}
